/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/variable/Module_interface.h"
#include "simodo/variable/VariableSetWrapper.h"
#include "simodo/variable/json/Serialization.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/reporter/Reporter_abstract.h"
#include "simodo/inout/token/Tokenizer.h"
#include "simodo/inout/token/RefBufferStream.h"
#include "simodo/variable/json/LexicalParametersLoader.h"

#include <memory>
#include <filesystem>
#include <cassert>

#ifdef CROSS_WIN
// MinGW related workaround
#define BOOST_DLL_FORCE_ALIAS_INSTANTIATION
#endif

#include <boost/dll/alias.hpp>

using namespace simodo;
using namespace simodo::variable;
using namespace simodo::inout;

namespace fs = std::filesystem;

namespace
{
    Value setup(Module_interface * host, const VariableSetWrapper & args);
    Value symbols(Module_interface * host, const VariableSetWrapper & args);
    Value comments(Module_interface * host, const VariableSetWrapper & args);
    Value produceTokens(Module_interface * host, const VariableSetWrapper & args);
}

class MainTokenizer : public Module_interface
{
    // ModuleFactory_interface *   _factory;
    LexicalParameters           _lexis;
    bool                        _lexis_ok = false;

public:
    // MainTokenizer(ModuleFactory_interface * factory) : _factory(factory) {}

    Value setup(const std::string & path_to_data, const std::string & language);
    Value symbols();
    Value comments();
    Value produceTokens(const std::u16string & text_to_parse, int position, context_index_t context);

    virtual version_t version() const override { return lib_version(); }

    virtual Object instantiate(std::shared_ptr<variable::Module_interface> module_object) override
    {
        return {{
            // {u"version", u"0.1"},
            {u"priority", 0},
            {u"setup", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::setup}},
                {{}, ValueType::String},
                {u"path_to_data", ValueType::String},
                {u"language", ValueType::String},
            }}}},
            {u"symbols", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::symbols}},
                {{}, ValueType::Array},
            }}}},
            {u"comments", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::comments}},
                {{}, ValueType::Array},
            }}}},
            {u"produceTokens", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::produceTokens}},
                {{}, ValueType::Object},
                {u"text_to_parse", ValueType::String},
                {u"position", ValueType::Int},
                {u"context", ValueType::Int},
            }}}},
        }};
    }

    // Factory method
    static std::shared_ptr<Module_interface> create() {
        return std::make_shared<MainTokenizer>();
    }
};

BOOST_DLL_ALIAS(
    MainTokenizer::create,    // <-- this function is exported with...
    create_simodo_module      // <-- ...this alias name
)

namespace
{
    Value setup(Module_interface * host, const VariableSetWrapper & args)
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);
        assert(args.size() == 2);
        assert(args[0].value().type() == ValueType::String);
        assert(args[1].value().type() == ValueType::String);

        MainTokenizer * main = static_cast<MainTokenizer *>(host);
        return main->setup(toU8(args[0].value().getString()),
                           toU8(args[1].value().getString()));
    }

    Value symbols(Module_interface * host, const VariableSetWrapper & )
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);

        MainTokenizer * main = static_cast<MainTokenizer *>(host);
        return main->symbols();
    }

    Value comments(Module_interface * host, const VariableSetWrapper & )
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);

        MainTokenizer * main = static_cast<MainTokenizer *>(host);
        return main->comments();
    }

    Value produceTokens(Module_interface * host, const VariableSetWrapper & args)
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);
        assert(args.size() == 3);
        assert(args[0].value().type() == ValueType::String);
        assert(args[1].value().type() == ValueType::Int);
        assert(args[2].value().type() == ValueType::Int);

        MainTokenizer *        main = static_cast<MainTokenizer *>(host);
        context_index_t context = NO_TOKEN_CONTEXT_INDEX;
        if (args[2].value().getInt() >= 0 )
            context = static_cast<context_index_t>(args[2].value().getInt());

        return main->produceTokens(args[0].value().getString(), args[1].value().getInt(), context);
    }

}

Value MainTokenizer::setup(const std::string & path_to_data, const std::string & language)
{
    fs::path path_to_lexis = path_to_data;
    path_to_lexis /= "lexis/" + language + ".json";

    if (!fs::exists(path_to_lexis))
        return u"Lexical data for " + toU16(language) + u" not found";

    _lexis_ok = loadLexicalParameters(path_to_lexis.string(), _lexis);
    if (!_lexis_ok)
        return u"Unable to load file '" + toU16(path_to_lexis.string()) + u"'";

    return u"";
}

Value MainTokenizer::symbols()
{
    std::vector<variable::Value> result; 

    for(const std::u16string & s : _lexis.punctuation_words) {
        Object symbol_data {{
            {u"symbol",   s},
            {u"type",     u"Keyword"},
            }};
        result.push_back(symbol_data);
    }

    return result;
}

Value MainTokenizer::comments()
{
    std::vector<variable::Value> result; 

    for(auto & m : _lexis.markups)
        if (m.type == LexemeType::Comment && m.end.empty()) {
            result.push_back(m.start);
            break;
        }

    for(auto & m : _lexis.markups)
        if (m.type == LexemeType::Comment && !m.end.empty()) {
            result.push_back(m.start);
            result.push_back(m.end);
            break;
        }

    return result;
}

Value MainTokenizer::produceTokens(const std::u16string & text_to_parse, int position, context_index_t context)
{
    if (!_lexis_ok)
        return Object {};

    RefBufferStream stream(text_to_parse.data());
    Tokenizer    tokenizer(0, stream, _lexis, context);
    Token        t = tokenizer.getAnyToken();
    Array   tokens;

    context = t.context();
    while(t.type() != LexemeType::Empty) {
        const char * type = t.qualification() != TokenQualification::None
                                ? getQualificationName(t.qualification())
                                : getLexemeTypeName(t.type());
        Object token_data {{
            {u"token",    t.token()},
            {u"lexeme",   t.lexeme()},
            {u"type",     inout::toU16(type)},
            {u"position", position + static_cast<int64_t>(t.location().range().start().character())},
            }};
        tokens.values().push_back(token_data.copy());
        context = t.context();
        t = tokenizer.getAnyToken();
    }

    return Object {{
        {u"tokens",  tokens.copy()},
        {u"context", static_cast<int64_t>(context == NO_TOKEN_CONTEXT_INDEX ? -1 : context)}
        }};
}

